#pragma once
#include "stdafx.h"

namespace ConsoleSandbox
{
	class FluidSimulator
	{

		float32x2 *m_pressurePingPong0; 
		float32x2 *m_pressurePingPong1;
		float32x2 *m_velocityPingPong0; 
		float32x2 *m_velocityPingPong1;

		float32x1 m_viscosity;
		float32x1 m_diffusion;
		float32x1 m_timeStep;

		uint32 m_interiorSizeWithBoundaries; // m_interiorSize + 2
		uint32 m_interiorSize; // m_interiorSizeWithBoundaries - 2
		uint32 m_arraySize; // m_interiorSizeWithBoundaries * m_interiorSizeWithBoundaries

#pragma region Props + Init

		REIGN_FORCEINLINING uint32 _at_(
			_In_ uint32x2 i
			)
		{
			return i.x + m_interiorSizeWithBoundaries * i.y;
		}

		REIGN_FORCEINLINING uint32 _at_(
			_In_ int32x2 i
			)
		{
			return i.x + m_interiorSizeWithBoundaries * i.y;
		}

		REIGN_FORCEINLINING uint32 _at_(
			_In_ float32x2 coords
			)
		{
			return _To_uint coords.x + m_interiorSizeWithBoundaries * _To_uint coords.y;
		}

		// Release memory of the dynamic resources
		void ReleaseAll()
		{
			_Delete_pointer_array(m_pressurePingPong0);
			_Delete_pointer_array(m_pressurePingPong1);
			_Delete_pointer_array(m_velocityPingPong0);
			_Delete_pointer_array(m_velocityPingPong1);
		}

		// Zero memory of the dynamic resources
		void ZeroAll()
		{
			ZeroValueArray(m_pressurePingPong0, m_arraySize);
			ZeroValueArray(m_pressurePingPong1, m_arraySize);
			ZeroValueArray(m_velocityPingPong0, m_arraySize);
			ZeroValueArray(m_velocityPingPong1, m_arraySize);
		}

		// Allocate memory for dynamic resources
		void AllocAll()
		{
			m_pressurePingPong0 = alloc_new float32x2[m_arraySize];
			m_pressurePingPong1 = alloc_new float32x2[m_arraySize];
			m_velocityPingPong0 = alloc_new float32x2[m_arraySize];
			m_velocityPingPong1 = alloc_new float32x2[m_arraySize];
		}

		// + ReleaseAll() + AllocAll() + ZeroAll()
		void Reset()
		{
			ReleaseAll();
			AllocAll();
			ZeroAll();
		}

#pragma endregion

	public:

#pragma region Props + Init

		FluidSimulator() : 
			m_pressurePingPong0(nullptr),
			m_pressurePingPong1(nullptr),
			m_velocityPingPong0(nullptr),
			m_velocityPingPong1(nullptr),
			m_timeStep(0.01f),
			m_viscosity(0.0f),
			m_diffusion(0.0f),
			m_interiorSize(0u),
			m_arraySize(0u)
		{
		}

		REIGN_HINTINLINING void TimeStep(
			_In_ float32x1 timeStep
			)
		{
			m_timeStep = timeStep;
		}

		// + Reset()
		REIGN_HINTINLINING void InteriorGridSize(
			_In_ uint32 interiorSize
			)
		{
			m_interiorSizeWithBoundaries = interiorSize + 2;
			m_arraySize = m_interiorSizeWithBoundaries * 
				m_interiorSizeWithBoundaries;
			m_interiorSize = interiorSize;

			Reset();
		}

#pragma endregion

	protected:

		float32x2 Advect(
			_In_ float32x2 gridCoords,
			_In_ float32x1 dt, // time step
			_In_ float32x1 rdx, // radix = 1
			_In_ float32x2 *inputQty, // input velocity
			_In_ float32x2 *outputQty // quantity to advect
			)
		{
			float32x2 pos = gridCoords - dt  * rdx * inputQty[_at_(gridCoords)];
			return Bilerp(inputQty, pos);
		}

		float32x2 Bilerp(
			_In_ float32x2 *quantity,
			_In_ float32x2 coords
			)
		{
			float32x1 dt0 = m_timeStep * m_interiorSize;
			float32x1 n = m_interiorSize;

			float32x2 xy;
			xy = coords - dt0 * quantity[_at_(coords)]; // trace back
			xy.Clamp(float32x2(0.5f, 0.5f),	float32x2(n + 0.5f, n + 0.5f)); // limit

			// interpolate

			int32x2 i; i.x = _To_int xy.x; i.y = i.x + 1;
			int32x2 j; j.x = _To_int xy.y; j.y = j.x + 1;

			float32x2 s; s.y = xy.x - i.x; s.x = 1.0f - s.x;
			float32x2 t; t.y = xy.y - j.x; t.x = 1.0f - t.x;

			int32x2 i00, i01, i10, i11;
			i00.x = i.x; i00.y = j.x; // i0 j0
			i01.x = i.x; i01.y = j.y; // i0 j1
			i10.x = i.y; i10.y = j.x; // i1 j0
			i11.x = i.y; i11.y = j.y; // i1 j1

			return
				s.x * (t.x * quantity[_at_(i00)] + t.y * quantity[_at_(i01)]) +
				s.y * (t.x * quantity[_at_(i10)] + t.y * quantity[_at_(i11)]);

		}

	};
};